package atessitura;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Sequencer.SyncMode;
import javax.sound.sampled.Clip;
import javax.swing.JFileChooser;
import javax.swing.Timer;



import midiwork.MidiFile;
import midiwork.MidiNote;
import midiwork.MidiTrack;
import midiwork.TimeSignature;



public class TModel 
{
	private TFController control;
	private Sequencer sequencer;
	private TimeSignature timeSig;	
	private float oneTick;	
	private MidiNote[][] notes;	
	private Timer timer;
	private MidiFile midiFile;
	private int currentTrack=0;
	
	//private double tempoFactor;
	
	private Guitar guitar;
	private Guitar [] guitarTracks;
	
	private boolean isLooping;
	private int loopWait=10;
	private int loopWaiting=0;
	
	public TModel(TFController tfController) 
	{
		control=tfController;
		isLooping=false;
		//tempoFactor=1;
		try {
			sequencer=MidiSystem.getSequencer();
		} catch (MidiUnavailableException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			//System.out.println("there was a problem setting the sequencer");
		}
		
	}
	
	//Menu Methods
	public void menuNew() {
		// TODO Auto-generated method stub
		
	}
	
	public void menuOpen(File myMidiFile) {
		try{
		Sequence seq= MidiSystem.getSequence(myMidiFile);
		sequencer.setSequence(seq);
		openSequencer();
		
		FileInputStream input=new FileInputStream(myMidiFile);
		midiFile=new MidiFile(read(myMidiFile),myMidiFile.getName());
		
		//System.out.println(midiFile.getTracks());
		setCurrentGuitarTrack(0);
		
		guitarTracks = new Guitar [midiFile.getTracks().size()];
		for (int x= 0; x<guitarTracks.length;x++)
		{
			setGuitarTrack(x);
			
		}
		
		
		control.setViewNotes(guitarTracks[0].getNotes());
		System.out.println("i'm setting the view: "+guitarTracks[0].getNotes().toString());
		setNotes();
		
		
		timeSig = midiFile.getTime();
		}
	    catch(IOException e)
	    {
	    	//System.out.println("MENUIMPORT ERROR "+e.printStackTrace());
	    	e.printStackTrace();
	    }
		catch(InvalidMidiDataException e)
		{
			//System.out.println("MENUIMPORT ERROR "+e);
			e.printStackTrace();
		} catch (MidiUnavailableException e) {
			// TODO Auto-generated catch block
			//System.out.println("MENUIMPORT ERROR "+e);
			e.printStackTrace();
		}
		setOneTickLength();
	}

	public void menuImport(File myMidiFile) {
		try{
			Sequence seq= MidiSystem.getSequence(myMidiFile);
			sequencer.setSequence(seq);
			openSequencer();
			
			FileInputStream input=new FileInputStream(myMidiFile);
			midiFile=new MidiFile(read(myMidiFile),myMidiFile.getName());
			
			//System.out.println(midiFile.getTracks());
			guitarTracks = new Guitar [midiFile.getTracks().size()];
			for (int x= 0; x<guitarTracks.length;x++)
			{
				setGuitarTrack(x);
				System.out.println(guitarTracks[x].getNumTicks());
			}
			
			setCurrentGuitarTrack(0);
			control.setViewNotes(guitarTracks[0].getNotes());
			setNotes();
			
			
			timeSig = midiFile.getTime();
		}
	    catch(IOException e)
	    {
	    	//System.out.println("MENUIMPORT ERROR "+e.printStackTrace());
	    	e.printStackTrace();
	    }
		catch(InvalidMidiDataException e)
		{
			//System.out.println("MENUIMPORT ERROR "+e);
			e.printStackTrace();
		} catch (MidiUnavailableException e) {
			// TODO Auto-generated catch block
			System.out.println("MENUIMPORT ERROR "+e);
			e.printStackTrace();
		}
		setOneTickLength();
	}
	
	public byte[] read(File file) throws IOException{


	    byte []buffer = new byte[(int) file.length()];
	    InputStream ios = null;
	    try {
	        ios = new FileInputStream(file);
	        if ( ios.read(buffer) == -1 ) {
	            throw new IOException("EOF reached while trying to read the whole file");
	        }        
	    } finally { 
	        try {
	             if ( ios != null ) 
	                  ios.close();
	        } catch ( IOException e) {
	        }
	    }

	    return buffer;
	}
	
	//General Sound Methods

	public void play() {
		try{
			if (!sequencer.isRunning())
			{
				if (getDuration()==getSeconds())
				{
					restartSequence();
				}
				startSequencer();
				control.playpauseswitch(true);
			}
			else
			{
				stopSequencer();
				control.playpauseswitch(false);
			}
		}
	    catch(Exception e)
	    {
	    	System.out.println("MENUPLAY ERROR "+e.toString());
	    }
		
	}
	
	

	public void stop(){
		try{
			restartSequence();
		}
	    catch(Exception e)
	    {
	    	System.out.println("MENUSTOP ERROR");
	    }
	}
	
	public void restartSequence()
	{
		try{
			stopSequencer();
			sequencer.setMicrosecondPosition(0);
		}
	    catch(Exception e)
	    {
	    	System.out.println("RELOADSEQUENCE ERROR");
	    }
		
	}
	
	public double getDuration() {
        double duration = 0.0;
        duration = sequencer.getSequence().getMicrosecondLength() / 1000000.0;
        return duration;
    }
	
	public double getSeconds() {
        double seconds = 0.0;
        try  {
           seconds = sequencer.getMicrosecondPosition() / 1000000.0;
        } catch (IllegalStateException e){
           System.out.println("TEMP: IllegalStateException "+
           "on sequencer.getMicrosecondPosition(): " + e);
        }
        return seconds;
    }
	
	public double getMillisecondPosition()
	{
		double milliseconds = 0.0;
        try  {
           milliseconds = sequencer.getMicrosecondPosition() / 1000.0;
        } catch (IllegalStateException e){
           System.out.println("TEMP: IllegalStateException "+
           "on sequencer.getMicrosecondPosition(): " + e);
        }
        return milliseconds;
	}
	
	public double getMillisecondLength()
	{
		double milliseconds = 0.0;
        try  {
           milliseconds = sequencer.getMicrosecondLength() / 1000.0;
        } catch (IllegalStateException e){
           System.out.println("TEMP: IllegalStateException "+
           "on sequencer.getMicrosecondPosition(): " + e);
        }
        return milliseconds;
	}

	public MidiNote getCurrentNote()
	{
		return getCurrentNote(currentTrack);
	}
	
	
	public MidiNote getCurrentNote(int track)
	{
		MidiNote now= notes[track][(int)sequencer.getTickPosition()];
		if (now!=null)
		{			
			//System.out.println(now);
			control.setNoteName(now.getScaleNote());
		}
		return now;
	}
	
	//Indirect Sequencer Manager
	public void openSequencer() throws MidiUnavailableException
	{
		sequencer.open();
		timer=new Timer((int)(1), new ActionListener(){
		
			@Override 
			public void actionPerformed(ActionEvent e) {
			//	System.out.println("tickposition: "+sequencer.getTickPosition() + "  "+ ((float)sequencer.getMicrosecondPosition())/(getOneTickLength()) + " ");
				getCurrentNote();
				if(isPaused())
				{
					if(isLooping && loopWaiting>0 && loopWaiting<loopWait)
		     		{
		     			loopWaiting++;
		     			System.out.println(loopWaiting);
		     		}
		     		else if(isLooping && loopWaiting==loopWait)
		     		{
		     			loopWaiting=0;
		     			play();
		     			System.out.println(loopWaiting);
		     		}
				}
		     	if (sequencer.getTickPosition()==sequencer.getTickLength())
		     	{	
		     		if(isLooping && loopWaiting==0)
		     		{
		     			sequencer.setMicrosecondPosition(0);
		     			loopWaiting++;
		     		}
		     		
		     		if(!isLooping)
		     		{
		     			timer.stop();
		     		}	
		     	}
		     	control.incrementalGUIUpdate(getPercentPlayed());
			}
			
		});
	}
	
	public void startSequencer()
	{
		
		timer.start();
		//sequencer.setTempoInMPQ(timeSig.getTempo());
		//System.out.println("sequencer time: "+sequencer.getMicrosecondPosition() + " tick: "+sequencer.getTickPosition());
		sequencer.start();
		//System.out.println("sequencer time: "+sequencer.getMicrosecondPosition() + " tick: "+sequencer.getTickPosition());
		
	} 
	
	public void setNotes()
	{
		ArrayList<MidiTrack> tracks=midiFile.getTracks();
		notes=new MidiNote[tracks.size()][(int)sequencer.getTickLength()+1];
		for (MidiTrack track:tracks)
		{
			ArrayList<MidiNote> notesA=track.getNotes();
			for(MidiNote note:notesA)
			{
				notes[tracks.indexOf(track)][note.getStartTime()]=note;
			}
		}
	}
	
	private void stopSequencer() {
		timer.stop();
		sequencer.stop();
	}
	
	public void setOneTickLength()
	{
		//The formula is 60000 / (BPM * PPQ) (milliseconds).
				float bpm = (float)sequencer.getTempoInBPM();
				float ppq = timeSig.getQuarter();
				//System.out.println("BPM: " + bpm+ " PPQ:"+ppq);
				oneTick= 60000000/(bpm*ppq);
				//System.out.println("microsecond length= "+ sequencer.getMicrosecondLength() + " tick length= "+sequencer.getTickLength() + "true tick length="+60000000/(bpm*ppq));
				//return (float)sequencer.getMicrosecondLength()/(float)sequencer.getTickLength();
				
				//System.out.println("TIMESIG TEMPO="+timeSig.getTempo()+" SEQUENCER TEMPO="+sequencer.getTempoInBPM()+ " "+sequencer.getTempoFactor()+" "+ sequencer.getTempoInMPQ());
	}
	
	public float getOneTickLength()
	{
		return oneTick;
	}
	
	
	public double getTickPosition() {
		return sequencer.getTickPosition();
	}

	public void setTime(int percent) {
		sequencer.setMicrosecondPosition((int)(((double)sequencer.getMicrosecondLength())*(((double)percent)/100)));
		
	}
	
	public double getPercentPlayed()
	{
		return (double)((double)sequencer.getMicrosecondPosition()/(double)sequencer.getMicrosecondLength());
	}

	public void octaveUp() 
	{
		for (Guitar guitar: guitarTracks)
		{
			guitar.octaveUp();
			control.setViewNotes(guitar.getNotes());
			control.repaintGPanel();
		}
	}
	
	public void octaveDown()
	{
		for (Guitar guitar: guitarTracks)
		{
			guitar.octaveDown();
			control.setViewNotes(guitar.getNotes());
			control.repaintGPanel();
		}
	}
	
	public int getNumTracks()
	{
		try{
			return midiFile.getTracks().size();
		}catch(Exception e)
		{
			return 0;
		}
		
	}
	
	public void setGuitarTrack(int track)
	{
		try {
			if(control.getGuitarTrack(track)!=null)
			{
				guitarTracks[track]=new Guitar(control.getGuitarTrack(track));
				control.setViewNotes(guitar.getNotes());
			}
			else
			{
				guitarTracks [track]= new Guitar(midiFile.getTracks().get(track), true);
			}
			
			//control.setViewNotes(guitar.getNotes());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public void setCurrentGuitarTrack(int track)
	{
		try {
			if(control.getGuitarTrack(track)!=null)
			{
				guitar=new Guitar(control.getGuitarTrack(track));
				control.setViewNotes(guitar.getNotes());
				return;
			}
			guitar=new Guitar(midiFile.getTracks().get(track), true);
			control.setViewNotes(guitar.getNotes());
			//control.setViewNotes(guitar.getNotes());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public ArrayList<GuitarNote>[] getNotesFromTrack (int t)
	{
		return guitarTracks [t].getNotes();
	}
	
	public String getStringTime()
	{
		return secondsToTimeStamp((int)(getMillisecondPosition()/1000));
		
	}
	
	public String getStringTimeLength()
	{
		return secondsToTimeStamp((int)(getMillisecondLength()/1000));
	}
	
	public String secondsToTimeStamp(int remainingSeconds)
	{
		/*String hours=""+(int)(remainingSeconds/(60*60));
		remainingSeconds=(int)(remainingSeconds%(60*60));
		String minutes=""+(int)(remainingSeconds/60);
		remainingSeconds=(int)(remainingSeconds%60);
		String seconds=""+remainingSeconds;
		if (minutes.length()==1)
		{
			minutes="0"+minutes;
		}
		if (seconds.length()==1)
		{
			seconds="0"+seconds;
		}
		return hours+":"+minutes+":"+seconds;*/
		//String hours=""+(int)(remainingSeconds/(60*60));
		remainingSeconds=(int)(remainingSeconds%(60*60));
		String minutes=""+(int)(remainingSeconds/60);
		remainingSeconds=(int)(remainingSeconds%60);
		String seconds=""+remainingSeconds;
		if (minutes.length()==1)
		{
			minutes=" "+minutes;
		}
		if (seconds.length()==1)
		{
			seconds="0"+seconds;
		}
		return minutes+":"+seconds;
	}

	public void setTempoFactor(double tempFact) {
		//double newTime=sequencer.getMicrosecondPosition()*(tempFact/tempoFactor);
		sequencer.setMicrosecondPosition(0);
		control.setTime(0);
		//sequencer.setMicrosecondPosition((long)newTime);
		//control.updateTime((sequencer.getMicrosecondPosition()/sequencer.getMicrosecondLength())*100);
		//sequencer.setTempoInBPM((int)(sequencer.getTempoInBPM()*(tempFact/tempoFactor)));
		//sequencer.setMicrosecondPosition(sequencer.getMicrosecondPosition()*(tempFact/tempoFactor));
		//sequencer.setTempoInBPM((float)((double)originalTempoBPM*tempFact));
		//tempoFactor=tempFact;
		sequencer.setTempoFactor((float)tempFact);
	}

	public boolean isPaused() {
		// TODO Auto-generated method stub
		//return paused;
		return !sequencer.isRunning();
	}

	public void setLooping(boolean loop)
	{
		isLooping=loop;
	}

}
